package com.gee.spring.boot.rpf.util.client;

import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONObject;
import com.gee.spring.boot.rpf.client.calculator.Calculator;
import com.gee.spring.boot.rpf.client.editor.nextclient.AbstractNextClientEditor;
import com.gee.spring.boot.rpf.client.AbstractClient;
import com.gee.spring.boot.rpf.client.BaseClientResponse;
import com.gee.spring.boot.rpf.common.constant.ClientConstant;
import com.gee.spring.boot.rpf.common.paramsource.ParamSourceTypeEnum;
import com.gee.spring.boot.rpf.config.cache.ClientCacheConfig;
import com.gee.spring.boot.rpf.controller.fico.RequestWrapper;
import com.gee.spring.boot.rpf.entity.*;
import com.gee.spring.boot.rpf.util.CommonUtil;
import com.gee.spring.boot.rpf.util.context.ApplicationContextUtil;
import com.gee.spring.boot.rpf.util.redis.RedisUtil;
import com.gee.thread.flow.common.result.ExecuteState;
import com.gee.thread.flow.common.result.WorkResult;
import com.gee.thread.flow.handler.AbstractWorkHandler;
import com.gee.thread.flow.translator.V2PTranslator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * desc:
 *
 * @author gee wrote on  2021-02-05 16:07:23
 */
@Component
public class ClientUtil {

    private final Logger logger = LoggerFactory.getLogger(ClientUtil.class);

    @Resource
    private RedisUtil redisUtil;

    public Client getClientByCode(String clientCode){
        if (StrUtil.isEmpty(clientCode)){
            return null;
        }
        return (Client) redisUtil.hget(ClientCacheConfig.CACHE_KEY_CLIENT_MAP,clientCode);
    }

    public List<ClientFlow> getClientFlowNoneDependList(String clientFlowId){
        if (StrUtil.isEmpty(clientFlowId)){
            return null;
        }
        return redisUtil.lGetALL(CommonUtil.buildRedisCacheKey(ClientCacheConfig.CACHE_KEY_CLIENT_FLOW__NONE_DEPEND_LIST
                , clientFlowId), ClientFlow.class);
    }

    public ClientFlow getClientFlow(Long serialNo){
        if (serialNo == null){
            return null;
        }
        return (ClientFlow)redisUtil.hget(ClientCacheConfig.CACHE_KEY_CLIENT_FLOW_MAP, serialNo.toString());
    }

    public List<RefClientFlow> getRefClientFlowList(Long clientFlowSerialNo){
        if (clientFlowSerialNo == null){
            return null;
        }
        return redisUtil.lGetALL(
                CommonUtil.buildRedisCacheKey(ClientCacheConfig.CACHE_KEY_REF_CLIENT_FLOW_LIST, clientFlowSerialNo)
                , RefClientFlow.class);
    }

    public List<RefClientFlowFactorInfo> getRefClientFlowFactorCalculator(Long clientFlowSerialNo){
        if (clientFlowSerialNo == null){
            return null;
        }
        return redisUtil.lGetALL(
                CommonUtil.buildRedisCacheKey(ClientCacheConfig.CACHE_KEY_REF_CLIENT_FLOW_FACTOR_INFO_LIST, clientFlowSerialNo)
                , RefClientFlowFactorInfo.class);
    }

    public FactorInfo getFactorInfo(Long serialNo){
        if (serialNo == null){
            return null;
        }
        return (FactorInfo)redisUtil.hget(ClientCacheConfig.CACHE_KEY_FACTOR_INFO_MAP,
                serialNo.toString());
    }

    public List<RefFactorInfoParamSource> getRefFactorInfoParamSourceList(Long serialNo){
        if (serialNo == null){
            return null;
        }
        String key = CommonUtil.buildRedisCacheKey(ClientCacheConfig.CACHE_KEY_REF_FACTOR_INFO_PARAM_SOURCE_LIST
                , serialNo);
        return redisUtil.lGetALL(key, RefFactorInfoParamSource.class);
    }

    public ParamSource getParamSource(Long serialNo){
        if (serialNo == null){
            return null;
        }
        return (ParamSource)redisUtil.hget(ClientCacheConfig.CACHE_KEY_PARAM_SOURCE_MAP, serialNo.toString());
    }

    public TempVariableInfo getTempVariableInfo(Long serialNo){
        if (serialNo == null){
            return null;
        }
        return (TempVariableInfo)redisUtil.hget(ClientCacheConfig.CACHE_KEY_TEMP_VARIABLE_INFO_MAP, serialNo.toString());
    }

    public List<RefClientFlowTempVariableInfo> getRefClientFlowTempVariableInfoList(Long clientFlowSerialNo){
        if (clientFlowSerialNo == null){
            return null;
        }
        String key = CommonUtil.buildRedisCacheKey(ClientCacheConfig.CACHE_KEY_REF_CLIENT_FLOW_TEMP_VARIABLE_INFO_LIST
                , clientFlowSerialNo);
        return redisUtil.lGetALL(key, RefClientFlowTempVariableInfo.class);
    }

    private List<RefTempVariableInfoParamSource> getRefTempVariableInfoParamSourceList(Long variableInfoSerialNo) {
        if (variableInfoSerialNo == null){
            return null;
        }
        String key = CommonUtil.buildRedisCacheKey(ClientCacheConfig.CACHE_KEY_REF_TEMP_VARIABLE_INFO_PARAM_SOURCE_LIST
                , variableInfoSerialNo);
        return redisUtil.lGetALL(key, RefTempVariableInfoParamSource.class);
    }

    public NextClientEditor getNextClientEditor(Long serialNo){
        if (serialNo == null){
            return null;
        }
        return (NextClientEditor)redisUtil.hget(ClientCacheConfig.CACHE_KEY_NEXT_CLIENT_EDITOR_MAP,
                serialNo.toString());
    }

    private List<RefNextClientEditorParamSource> getRefNextClientEditorParamSourceList(Long editorSerialNo) {
        if (editorSerialNo == null){
            return null;
        }
        String key = CommonUtil.buildRedisCacheKey(ClientCacheConfig.CACHE_KEY_REF_NEXT_CLIENT_EDITOR_PARAM_SOURCE_LIST
                , editorSerialNo);
        return redisUtil.lGetALL(key, RefNextClientEditorParamSource.class);
    }

    public List<RefClientFlowNextClientEditor> getRefClientFlowNextClientEditorList(Long clientFlowSerialNo){
        if (clientFlowSerialNo == null){
            return null;
        }
        return redisUtil.lGetALL(
                CommonUtil.buildRedisCacheKey(ClientCacheConfig.CACHE_KEY_REF_CLIENT_FLOW_NEXT_CLIENT_EDITOR_LIST, clientFlowSerialNo)
                , RefClientFlowNextClientEditor.class);
    }

    public AbstractClient<?,?> createAbstractClient(ClientFlow clientFlow ){
        Client client = getClientByCode(clientFlow.getClientCode());
        AbstractClient<?,?> abstractClient = null;
        try {
            abstractClient = (AbstractClient) ApplicationContextUtil.getBean(client.getClientBeanName());
        } catch (Exception e) {
            logger.error("createAbstractClient exception", e);
            return null;
        }
        if (abstractClient.needV2PTranslator()){
            V2PTranslator v2PTranslator = null;
            try {
                v2PTranslator = ApplicationContextUtil.getBean(clientFlow.getRequestInitBeanName(), V2PTranslator.class);
            } catch (Exception e) {
                logger.error("createAbstractClient needV2PTranslator but bean not exists ", e);
                return null;
            }
            abstractClient.setV2PTranslator(v2PTranslator);
        }
        Map<String, List<Object>> extraProperties = new HashMap<>();

        List<RefClientFlowTempVariableInfo> refClientFlowTempVariableInfoList = getRefClientFlowTempVariableInfoList(clientFlow.getSerialNo());
        List<Object> tempVariableInfoList = new ArrayList<>();
        refClientFlowTempVariableInfoList.forEach(refClientFlowTempVariableInfo -> {
            TempVariableInfo tempVariableInfo = getTempVariableInfo(refClientFlowTempVariableInfo.getTempVariableInfoSerialNo());
            tempVariableInfoList.add(tempVariableInfo);
        });
        extraProperties.put(ClientConstant.CLIENT_FLOW_TEMP_VARIABLE, tempVariableInfoList);

        List<RefClientFlowFactorInfo> refClientFlowFactorCalculator = getRefClientFlowFactorCalculator(clientFlow.getSerialNo());
        List<Object> factorInfoList = new ArrayList<>();
        refClientFlowFactorCalculator.forEach(ref -> {
            FactorInfo factorInfo = getFactorInfo(ref.getFactorInfoSerialNo());
            factorInfoList.add(factorInfo);
        });
        extraProperties.put(ClientConstant.CLIENT_FLOW_FACTOR_INFO, factorInfoList);

        List<RefClientFlowNextClientEditor> refClientFlowNextClientEditorList = getRefClientFlowNextClientEditorList(clientFlow.getSerialNo());
        List<Object> nextClientEditorList = new ArrayList<>();
        refClientFlowNextClientEditorList.forEach(ref -> {
            NextClientEditor nextClientEditor = getNextClientEditor(ref.getNextClientEditorSerialNo());
            nextClientEditorList.add(nextClientEditor);
        });
        extraProperties.put(ClientConstant.CLIENT_FLOW_NEXT_CLIENT_EDITOR, nextClientEditorList);

        abstractClient.setId(clientFlow.getClientId());
        abstractClient.setClientMeta(client);
        abstractClient.setExtraProperties(extraProperties);
        String clientHandler = clientFlow.getClientHandler();
        if (clientHandler != null){
            AbstractWorkHandler workHandler = null;
            try {
                workHandler = ApplicationContextUtil.getBean(clientHandler, AbstractWorkHandler.class);
            } catch (Exception e) {
                logger.error("createAbstractClient clientHandler bean not exists ", e);
                return null;
            }
            abstractClient.setWorkHandler(workHandler);
        }
        return abstractClient;
    }

    public Object calculateFactor(String id, ExecuteState currentWorkExecuteState,
                                  FactorInfo factorInfo, RequestWrapper requestWrapper,
                                   Map<String, WorkResult<?, ?>> workResultMap){
        if (factorInfo == null){
            return null;
        }
        List<RefFactorInfoParamSource> refList = getRefFactorInfoParamSourceList(factorInfo.getSerialNo());
        List<Object> args = new ArrayList<>();
        BaseClientResponse clientResponse = (BaseClientResponse)workResultMap.get(id).getResult();
        for (RefFactorInfoParamSource ref : refList) {
            Object arg = getArg(ref.getParamSourceSerialNo(), requestWrapper, workResultMap);
            args.add(arg);
        }
        Calculator calculator = null;
        try {
            calculator = (Calculator) ApplicationContextUtil.getBean(factorInfo.getCalculatorBeanName());
        } catch (Exception e) {
            logger.error("client [{}] factor [{}]'s calculator [{}] not exists", id, factorInfo.getFactorName(),
                    factorInfo.getCalculatorBeanName());
            return null;
        }
        Class factorTypeClass = getFactorType(factorInfo.getFactorType());
        Object factor;
        try {
            factor = calculator.calculate(currentWorkExecuteState, clientResponse, factorTypeClass, args.toArray());
        } catch (Exception e) {
            logger.error("calculate  factor exceptionally : {}", factorInfo.getFactorName(), e);
            factor = calculator.getCalculateExceptionFactor(factorTypeClass);
        }
        return factor;
    }

    public Object calculateTempVariable(String id, ExecuteState currentWorkExecuteState,
                                        TempVariableInfo tempVariableInfo, RequestWrapper requestWrapper,
                                        Map<String, WorkResult<?, ?>> workResultMap){
        if (tempVariableInfo == null){
            return null;
        }
        List<RefTempVariableInfoParamSource> refList = getRefTempVariableInfoParamSourceList(tempVariableInfo.getSerialNo());
        List<Object> args = new ArrayList<>();
        for (RefTempVariableInfoParamSource ref : refList) {
            Object arg = getArg(ref.getParamSourceSerialNo(), requestWrapper, workResultMap);
            args.add(arg);
        }
        BaseClientResponse clientResponse = (BaseClientResponse)workResultMap.get(id).getResult();

        Calculator calculator = null;
        try {
            calculator = (Calculator) ApplicationContextUtil.getBean(tempVariableInfo.getCalculatorBeanName());
        } catch (Exception e) {
            logger.error("client [{}] tempVariable [{}]'s calculator {} not exists", id,
                    tempVariableInfo.getVariableName(), tempVariableInfo.getCalculatorBeanName());
            return null;
        }
        Class variableTypeClass = getFactorType(tempVariableInfo.getVariableType());
        Object factor;
        try {
            factor = calculator.calculate(currentWorkExecuteState, clientResponse, variableTypeClass, args.toArray());
        } catch (Exception e) {
            logger.error("client [{}] calculate tempVariable [{}]exceptionally : {}", id, tempVariableInfo.getVariableName(), e);
            factor = null;
        }
        return factor;
    }

    public List<ClientFlow> calculateNextClient(String id, ExecuteState currentWorkExecuteState,
                                                NextClientEditor nextClientEditor, RequestWrapper requestWrapper,
                                          Map<String, WorkResult<?,?>> workResultMap) {
        if (nextClientEditor == null){
            return null;
        }
        List<RefNextClientEditorParamSource> refList = getRefNextClientEditorParamSourceList(nextClientEditor.getSerialNo());
        List<Object> args = new ArrayList<>();
        for (RefNextClientEditorParamSource ref : refList) {
            Object arg = getArg(ref.getParamSourceSerialNo(), requestWrapper, workResultMap);
            args.add(arg);
        }
        BaseClientResponse currentWorkResponse = (BaseClientResponse)workResultMap.get(id).getResult();
        AbstractNextClientEditor editor = null;
        try {
            editor = ApplicationContextUtil.getBean(nextClientEditor.getEditorBeanName(),AbstractNextClientEditor.class);
        } catch (Exception e) {
            logger.error("client [{}] nextClientEditor {} not exists", id, nextClientEditor.getEditorBeanName());
            return null;
        }
        List<ClientFlow> clientFlowList = null;
        try {
            clientFlowList =
                    editor.getNextClientFlow(currentWorkExecuteState, currentWorkResponse, args.toArray());
        } catch (Exception e) {
            logger.error("calculate  factor exceptionally : {}", nextClientEditor.getEditorName(), e);
        }
        return clientFlowList;
    }

    private Object getArg(Long paramSourceSerialNo, RequestWrapper requestWrapper,
                          Map<String, WorkResult<?,?>> workResultMap){
        ParamSource paramSource = getParamSource(paramSourceSerialNo);
        JSONObject request = null;
        ParamSourceTypeEnum paramSourceTypeEnum = getParamSourceTypeEnum(paramSource.getParamSourceType());
        if (paramSourceTypeEnum == ParamSourceTypeEnum.REQUEST) {
            request = requestWrapper.getRequest();
        }
        if (paramSourceTypeEnum == ParamSourceTypeEnum.CAL_VARIABLE) {
            request = requestWrapper.getTempVariable();
        }
        if (paramSourceTypeEnum == ParamSourceTypeEnum.FACTOR) {
            request = requestWrapper.getFactors();
        }
        if (request != null) {
            String paramName = paramSource.getParamName();
            if (! request.containsKey(paramName)){
                logger.error("param {} not exists in {}", paramName , paramSourceTypeEnum);
            }
            return request.get(paramName);
        }
        if (paramSourceTypeEnum == ParamSourceTypeEnum.CLIENT) {
            return workResultMap.get(paramSource.getClientId());
        }
        return null;
    }
    private Class getFactorType(Integer factorType) {
        switch (factorType){
            case 2:
                return Integer.class;
            case 3:
                return Double.class;
            default:
                return String.class;
        }
    }



    private ParamSourceTypeEnum getParamSourceTypeEnum(Integer paramSourceType){
        if (paramSourceType == null){
            return ParamSourceTypeEnum.UNKNOWN;
        }
        switch (paramSourceType){
            case 1:
                return ParamSourceTypeEnum.REQUEST;
            case 2:
                return ParamSourceTypeEnum.CAL_VARIABLE;
            case 3:
                return ParamSourceTypeEnum.CLIENT;
            case 4:
                return ParamSourceTypeEnum.FACTOR;
            default:
                return ParamSourceTypeEnum.UNKNOWN;
        }

    }
}
